package com.vst.orm.search.hibernate;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vst.orm.search.BaseSearchProcessor;
import com.vst.orm.search.Field;
import com.vst.orm.search.ISearch;
import com.vst.orm.search.SearchResult;
import com.vst.orm.util.InternalUtil;
import com.vst.orm.util.SearchUtil;

public class HibernateSearchProcessor
  extends BaseSearchProcessor
{
  private static Logger logger = LoggerFactory.getLogger(HibernateSearchProcessor.class);
  private static Map<SessionFactory, HibernateSearchProcessor> map = new HashMap();
  
  public static HibernateSearchProcessor getInstanceForSessionFactory(SessionFactory sessionFactory)
  {
    HibernateSearchProcessor instance = (HibernateSearchProcessor)map.get(sessionFactory);
    if (instance == null)
    {
      instance = new HibernateSearchProcessor(HibernateMetadataUtil.getInstanceForSessionFactory(sessionFactory));
      map.put(sessionFactory, instance);
    }
    return instance;
  }
  
  private HibernateSearchProcessor(HibernateMetadataUtil mdu)
  {
    super(QLTYPE_HQL, mdu);
  }
  
  public List search(Session session, ISearch search)
  {
    if (search == null) {
      return null;
    }
    return search(session, search.getSearchClass(), search);
  }
  
  public List search(Session session, Class<?> searchClass, ISearch search)
  {
    if ((searchClass == null) || (search == null)) {
      return null;
    }
    List<Object> paramList = new ArrayList();
    String hql = generateQL(searchClass, search, paramList);
    Query query = session.createQuery(hql);
    addParams(query, paramList);
    addPaging(query, search);
    addResultMode(query, search);
    
    return query.list();
  }
  
  public int count(Session session, ISearch search)
  {
    if (search == null) {
      return 0;
    }
    return count(session, search.getSearchClass(), search);
  }
  
  public int count(Session session, Class<?> searchClass, ISearch search)
  {
    if ((searchClass == null) || (search == null)) {
      return 0;
    }
    List<Object> paramList = new ArrayList();
    String hql = generateRowCountQL(searchClass, search, paramList);
    if (hql == null) {
      return 1;
    }
    Query query = session.createQuery(hql);
    addParams(query, paramList);
    
    return ((Number)query.uniqueResult()).intValue();
  }
  
  public SearchResult searchAndCount(Session session, ISearch search)
  {
    if (search == null) {
      return null;
    }
    return searchAndCount(session, search.getSearchClass(), search);
  }
  
  public SearchResult searchAndCount(Session session, Class<?> searchClass, ISearch search)
  {
    if ((searchClass == null) || (search == null)) {
      return null;
    }
    SearchResult result = new SearchResult();
    result.setResult(search(session, searchClass, search));
    if (search.getMaxResults() > 0) {
      result.setTotalCount(count(session, searchClass, search));
    } else {
      result.setTotalCount(result.getResult().size() + SearchUtil.calcFirstResult(search));
    }
    return result;
  }
  
  public Object searchUnique(Session session, ISearch search)
    throws NonUniqueResultException
  {
    if (search == null) {
      return null;
    }
    return searchUnique(session, search.getSearchClass(), search);
  }
  
  public Object searchUnique(Session session, Class<?> entityClass, ISearch search)
    throws NonUniqueResultException
  {
    if (search == null) {
      return null;
    }
    List<Object> paramList = new ArrayList();
    String hql = generateQL(entityClass, search, paramList);
    Query query = session.createQuery(hql);
    addParams(query, paramList);
    addResultMode(query, search);
    
    return query.uniqueResult();
  }
  
  private void addParams(Query query, List<Object> params)
  {
    StringBuilder debug = null;
    
    int i = 1;
    for (Object o : params)
    {
      if (logger.isDebugEnabled())
      {
        if (debug == null) {
          debug = new StringBuilder();
        } else {
          debug.append("\n\t");
        }
        debug.append("p");
        debug.append(i);
        debug.append(": ");
        debug.append(InternalUtil.paramDisplayString(o));
      }
      if ((o instanceof Collection)) {
        query.setParameterList("p" + Integer.toString(i++), (Collection)o);
      } else if ((o instanceof Object[])) {
        query.setParameterList("p" + Integer.toString(i++), (Object[])o);
      } else {
        query.setParameter("p" + Integer.toString(i++), o);
      }
    }
    if ((debug != null) && (debug.length() != 0)) {
      logger.debug(debug.toString());
    }
  }
  
  private void addPaging(Query query, ISearch search)
  {
    int firstResult = SearchUtil.calcFirstResult(search);
    if (firstResult > 0) {
      query.setFirstResult(firstResult);
    }
    if (search.getMaxResults() > 0) {
      query.setMaxResults(search.getMaxResults());
    }
  }
  
  private void addResultMode(Query query, ISearch search)
  {
    int resultMode = search.getResultMode();
    if (resultMode == 0)
    {
      int count = 0;
      Iterator<Field> fieldItr = search.getFields().iterator();
      while (fieldItr.hasNext())
      {
        Field field = (Field)fieldItr.next();
        if ((field.getKey() != null) && (!field.getKey().equals("")))
        {
          resultMode = 3;
          break;
        }
        count++;
      }
      if (resultMode == 0) {
        if (count > 1) {
          resultMode = 1;
        } else {
          resultMode = 4;
        }
      }
    }
    switch (resultMode)
    {
    case 1: 
      query.setResultTransformer(ARRAY_RESULT_TRANSFORMER);
      break;
    case 2: 
      query.setResultTransformer(Transformers.TO_LIST);
      break;
    case 3: 
      List<String> keyList = new ArrayList();
      Iterator<Field> fieldItr = search.getFields().iterator();
      while (fieldItr.hasNext())
      {
        Field field = (Field)fieldItr.next();
        if ((field.getKey() != null) && (!field.getKey().equals(""))) {
          keyList.add(field.getKey());
        } else {
          keyList.add(field.getProperty());
        }
      }
      query.setResultTransformer(new MapResultTransformer((String[])keyList.toArray(new String[0])));
      break;
    }
  }
  
  private static final ResultTransformer ARRAY_RESULT_TRANSFORMER = new ResultTransformer()
  {
    private static final long serialVersionUID = 1L;
    
    public List transformList(List collection)
    {
      return collection;
    }
    
    public Object transformTuple(Object[] tuple, String[] aliases)
    {
      return tuple;
    }
  };
  
  private static class MapResultTransformer
    implements ResultTransformer
  {
    private static final long serialVersionUID = 1L;
    private String[] keys;
    
    public MapResultTransformer(String[] keys)
    {
      this.keys = keys;
    }
    
    public List transformList(List collection)
    {
      return collection;
    }
    
    public Object transformTuple(Object[] tuple, String[] aliases)
    {
      Map<String, Object> map = new HashMap();
      for (int i = 0; i < this.keys.length; i++)
      {
        String key = this.keys[i];
        if (key != null) {
          map.put(key, tuple[i]);
        }
      }
      return map;
    }
  }
}

